/*
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is RhinoDB, released August 08, 2008.
 * 
 * Contributor(s): Alexandru Chiculita.
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 * 
 * ***** END LICENSE BLOCK ***** */

package org.achicu.db;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.achicu.db.logics.CollectionLogic;
import org.achicu.db.logics.FloatSortedCollectionLogic;
import org.achicu.db.logics.IntegerSortedCollectionLogic;
import org.achicu.db.logics.StringSortedCollectionLogic;
import org.achicu.db.values.AbstractStorableValue;
import org.achicu.db.values.StorableObject;
import org.achicu.db.values.StorableValue;

public class StorableCollection extends AbstractStorableValue implements Cloneable {
	public static final int TYPE = 5; 
		
	
	public int getType() {
		return TYPE;
	}
		
	private int id;
	
	private CollectionLogic logic;
	
	
	public StorableCollection(Store store, int id) throws InstantiationException, IllegalAccessException, StorageException {
		this.id = id;
		this.store = store;
		if(store==null){
			throw new StorageException("No store has been set.");
		}
		this.logic = CollectionLogicFactory.getInstance().getCollectionLogicByType(store, this.id, store.getCollectionType(id));
	}
	
	public StorableCollection(){
		id = -1;
	}
	
	public void setStore(Store store){
		if(store==null || this.id==-1){
			this.store = store;
			this.id = store.createEmptyCollection(Store.NO_INDEX);
			this.logic = new CollectionLogic(store, this.id);
		}
	}
	
	public void setInheritedCollection(Store store, int inheritedId) throws StorageException{
		this.store = store;
		
		if(logic == null) 
			throw new StorageException("No collection logic has been set.");
		
		this.id = store.inheritCollection(inheritedId, logic.getCollectionType());
		logic.setCollectionId(id);
		
		// filter all the objects here
		
		
		try {
			StorableCollection parent = new StorableCollection(store, inheritedId);
			StorableObject[] items = parent.list(0,Integer.MAX_VALUE);
			for(int i=0;i<items.length; i++){
				StorableObject item = items[i];
				push(item, true);
			}
		} catch (Throwable e){
			e.printStackTrace();
		}	
		
	}
	
	
	public void readFrom(ObjectInputStream input)
			throws IOException, InstantiationException, IllegalAccessException {
		int id = input.readInt();
		
		if(id!=-1){
			this.id = id;
		}else{
			this.id = store.createEmptyCollection(Store.NO_INDEX);
		}
		
		this.logic = CollectionLogicFactory.getInstance().getCollectionLogicByType(store, this.id, store.getCollectionType(id));
	}

	
	public void writeTo(ObjectOutputStream output)
			throws IOException {
		output.writeInt( this.id );
	}
	
	public boolean push(StorableObject object) throws StorageException{
		return push(object, false);
	}
	
	public boolean add(StorableObject object) throws StorageException{
		return push(object);
	}
	
	public boolean remove(StorableObject object) throws StorageException{
		if(object.getId()==-1) 
			return false;
		
		return remove(object, false);
	}
	
	public StorableObject getById(int id){
		return new StorableObject(store, id);
	}
	
	protected boolean push(StorableObject object, boolean update) throws StorageException {
		if(this.logic == null || this.id==-1)
		{
			this.id = store.createEmptyCollection(Store.NO_INDEX);
			logic = new CollectionLogic(store, this.id);			
		}
		
		if(update == false && logic.getCollectionType() != Store.NO_INDEX){
			throw new StorageException("Just the basic collections can push new elements.");
		}
				
		if(!logic.canAdd(object))
			return false;
		
		object.setStore(store);
		object.saveToStore();
		
		logic.add(object);
		
		Integer [] collections = store.getInheritingCollections(this.id);
		for(int i = 0; i < collections.length; i++){
			try {
				StorableCollection collection = new StorableCollection(store, collections[i].intValue());
				collection.push(object, true);
			} catch(StorageException e1){
				throw e1;
			} catch (Throwable e) {
				throw new StorageException(e);
			}
		}
		
		return true;
	}
	
	protected boolean remove(StorableObject object, boolean update) throws StorageException {
		if(this.logic == null || this.id==-1)
		{
			return false;
		}
		
		if(update == false && logic.getCollectionType() != Store.NO_INDEX){
			throw new StorageException("Just the basic collections can remove elements.");
		}
				
		if(!logic.canAdd(object))
			return false;
		
		logic.remove(object);
		
		Integer [] collections = store.getInheritingCollections(this.id);
		for(int i = 0; i < collections.length; i++){
			try {
				StorableCollection collection = new StorableCollection(store, collections[i].intValue());
				collection.remove(object, true);
			} catch(StorageException e1){
				throw e1;
			} catch (Throwable e) {
				throw new StorageException(e);
			}
		}
		
		return true;
	}
	
	public StorableObject [] list(int skip, int limit) throws StorageException{
		if(this.logic == null)
			throw new StorageException("No collection logic has been set.");
		
		if(this.id==-1) 
			return null;
		
		Integer[] items = logic.getItems(skip, limit);
		StorableObject [] result = new StorableObject[items.length];
		for(int i=0; i<items.length; i++){
			result[i] = new StorableObject( store, items[i].intValue() );
		}
		return result;
	}
	
	public StorableCollection reverse(){
		StorableCollection result = null;
		try {
			result = (StorableCollection)this.clone();
		
			if(logic!=null){
				result.logic = logic.cloneLogic();
				result.logic.setReverse(!logic.isReversed());
			}		
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return result;
	}
	
	public StorableCollection sortByInt(String property) throws StorageException{
		if(this.id == -1) 
			throw new StorageException("The collection hasn't been created yet.");
		
		int newCollection = store.getCollectionByConfig( this.id, IntegerSortedCollectionLogic.getLogicConfig(property) );
		
		StorableCollection result = null;
		
		if(newCollection!=-1){
			//if we've already loaded the collection, just use it
			try{
				result = new StorableCollection(store, newCollection);
			}catch (Throwable e){
				throw new StorageException(e);
			}
		}else{
			result = new StorableCollection();
			result.logic = new IntegerSortedCollectionLogic(store, property);
			result.setInheritedCollection(store, this.id);
		}
		
		return result;
		
	}
	
	public StorableCollection sortByFloat(String property) throws StorageException{
		if(this.id == -1) 
			throw new StorageException("The collection hasn't been created yet.");
		
		int newCollection = store.getCollectionByConfig( this.id, FloatSortedCollectionLogic.getLogicConfig(property) );
		
		StorableCollection result = null;
		
		if(newCollection!=-1){
			//if we've already loaded the collection, just use it
			try{
				result = new StorableCollection(store, newCollection);
			}catch (Throwable e){
				throw new StorageException(e);
			}
		}else{
			result = new StorableCollection();
			result.logic = new FloatSortedCollectionLogic(store, property);
			result.setInheritedCollection(store, this.id);
		}
		
		return result;
		
	}
	
	public StorableCollection sortByString(String property) throws StorageException{
		if(this.id == -1) 
			throw new StorageException("The collection hasn't been created yet.");
		
		int newCollection = store.getCollectionByConfig( this.id, StringSortedCollectionLogic.getLogicConfig(property) );
		
		StorableCollection result = null;
		
		if(newCollection!=-1){
			//if we've already loaded the collection, just use it
			try{
				result = new StorableCollection(store, newCollection);
			}catch (Throwable e){
				throw new StorageException(e);
			}
		}else{
			result = new StorableCollection();
			result.logic = new StringSortedCollectionLogic(store, property);
			result.setInheritedCollection(store, this.id);
		}
		
		return result;
		
	}

	public StorableObject[] getItems(Object obj, int skip, int limit) throws StorageException{
		if(this.id == -1) 
			throw new StorageException("The collection hasn't been created yet.");
		
		if(this.logic == null)
			throw new StorageException("No collection logic has been set.");
		
		StorableValue value = StorableValueFactory.wrapValue(obj);
		
		Integer[] items = logic.getItems(value, skip, limit);
		if(items==null)
			return null;
		
		StorableObject [] result = new StorableObject[items.length];
		for(int i=0; i<items.length; i++){
			result[i] = new StorableObject( store, items[i].intValue() );
		}
		return result;		
	}
	
	public boolean created(){
		return (this.id != -1 && this.logic != null);
	}
	
	
	public Object unwrap() {
		return this;
	}
}
